package com.alan.sso.share.filter;

import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.symmetric.AES;
import cn.hutool.http.HttpUtil;
import com.alan.sso.share.bean.Constant;
import com.alan.sso.share.bean.SsoConfigData;
import com.alan.sso.share.bean.SsoUser;
import com.alan.sso.share.utils.SsoCookieUtils;
import com.alan.sso.share.utils.SsoSessionStorage;
import com.alan.sso.share.utils.URIBuilder;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import javax.servlet.*;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.Enumeration;

/**
 * @Auther: Alan liu
 * @Date: 2018/12/20 16:57
 * @Description: SsoAuthenticationFilter
 */
@Slf4j
public class SsoAuthenticationFilter implements Filter {

    private int cookieMaxAge = 3600;
    public static String encoding = "UTF-8";
    private String appName = "";

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        //参数初始化 简单处理
        SsoConfigData ssoConfigData = SsoConfigData.newInstance();
        ssoConfigData.setAppName(filterConfig.getInitParameter(Constant.K_APPNAME));
        appName = ssoConfigData.getAppName();
        ssoConfigData.setAppKey(filterConfig.getInitParameter(Constant.K_APPKEY));
        ssoConfigData.setLoginUrl(filterConfig.getInitParameter(Constant.K_LOGINURL));
        ssoConfigData.setLogoutUrl(filterConfig.getInitParameter(Constant.K_LOGOUTURL));
        ssoConfigData.setValidateUrl(filterConfig.getInitParameter(Constant.K_VALIDATEURL));
        String exclusive = filterConfig.getInitParameter(Constant.K_EXCLUSIVE);
        if (!StringUtils.isEmpty(exclusive)) {
            ssoConfigData.addPathPattern(exclusive.split(","));
        }
        //初始化aes
        AES aes = SecureUtil.aes(ssoConfigData.getAppKey().getBytes());
        ssoConfigData.setAes(aes);
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest)servletRequest;
        HttpServletResponse response = (HttpServletResponse)servletResponse;
        SsoConfigData ssoConfigData = SsoConfigData.newInstance();
        if (ssoConfigData.getPathPattern() != null && ssoConfigData.getPathPattern().matches(request)) {
            // 要排除的
            filterChain.doFilter(servletRequest, servletResponse);
        } else {
            //检查session是否是登录了
            SsoUser ssoUser = SsoCookieUtils.parseStaffFromSessionCookie(request);
            if(ssoUser != null){
                //已登录
                //当前系统中保存用户信息
                SsoSessionStorage.getInstance().putIfAbsent(ssoUser);
                //执行下一步
                filterChain.doFilter(servletRequest, servletResponse);
            } else {
                //未登录 取 ticket
                String ticket = this.retrieveTicket(request);
                if(StringUtils.isBlank(ticket)){
                    if (this.isAjax(request)) {
                        response.setHeader("sso-timeout", "true");
                        response.getWriter().println(this.ajaxResponse());
                    } else {
                        this.redirectToLogin(request, response);
                    }
                } else {
                    //调用sso检查ticket并取得用户信息
                    String validateResult = this.validateTicket(ticket);
                    SsoUser tempUser = this.parseValidateResult(validateResult);
                    if (tempUser == null) {
                        //ticket不通过，返回错误信息或到登录页面
                        if (this.isAjax(request)) {
                            response.setHeader("sso-timeout", "true");
                            response.getWriter().println(this.ajaxResponse());
                        } else {
                            this.redirectToLogin(request, response);
                        }
                    } else {
                        String result = JSONObject.toJSONString(tempUser);
                        //保存用户信息到cookies中
                        this.setSession(response, result);
                        request.setAttribute(Constant.K_SESSION_ATTR_KEY, ssoConfigData.getAes().encryptBase64(result));
                        //保存用户信息到SsoSessionStorage
                        SsoSessionStorage.getInstance().putIfAbsent(tempUser);
                        filterChain.doFilter(servletRequest, servletResponse);
                    }
                }
            }
        }
    }

    @Override
    public void destroy() {

    }

    private String ajaxResponse() {
        JSONObject json = new JSONObject();
        json.put("code", -100);
        json.put("msg", "user not login");
        return json.toJSONString();
    }

    private boolean isAjax(HttpServletRequest request) {
        String header = request.getHeader("X-Requested-With");
        return StringUtils.equalsIgnoreCase(header, "XMLHttpRequest");
    }

    /**
     * @Author Alan liu
     * @Description 解析ticket验证返回的内容，解析成用户实体
     * @Date 11:51 2018/12/21
     * @param validateResult
     * @return com.alan.sso.share.bean.SsoUser
     **/
    protected SsoUser parseValidateResult(String validateResult) {
        try {
            SsoUser ssoUser = JSONObject.parseObject(validateResult, SsoUser.class);
            if ( ssoUser != null && StringUtils.isNotBlank(ssoUser.getUserName()) ) {
                return ssoUser;
            }
        } catch (Exception var3) {
            log.error("Parse validate result failed.", var3);
        }
        return null;
    }

    /**
     * @Author Alan liu
     * @Description 验证ticket
     * @Date 11:46 2018/12/21
     * @param serviceTicket
     * @return java.lang.String
     **/
    protected String validateTicket(String serviceTicket) {
        try {
            SsoConfigData ssoConfigData = SsoConfigData.newInstance();
            String target = URLEncoder.encode(this.appName, encoding);
            String url = (new URIBuilder(ssoConfigData.getValidateUrl())).addParameter(Constant.K_TICKET, serviceTicket).addParameter(Constant.K_APPNAME, target).toString();
            String result = HttpUtil.get(url);
            return result;
        } catch (Exception var5) {
            log.error("Validate ticket failed.", var5);
            return null;
        }
    }

    /**
     * @Author Alan liu
     * @Description 跳转到sso登录页面
     * @Date 11:47 2018/12/21
     * @param request
     * @param response
     * @return void
     **/
    private void redirectToLogin(HttpServletRequest request, HttpServletResponse response) {
        try {
            SsoConfigData ssoConfigData = SsoConfigData.newInstance();
            String encodeAppName = URLEncoder.encode(this.appName, "UTF-8");
            URIBuilder targetURL = new URIBuilder(request.getRequestURL().toString());
            Enumeration params = request.getParameterNames();
            String param;
            while(params != null && params.hasMoreElements()) {
                param = (String)params.nextElement();
                if (!StringUtils.equals("ticket", param)) {
                    targetURL.addParameter(param, request.getParameter(param));
                }
            }
            param = URLEncoder.encode(targetURL.toString(), "UTF-8");
            String url = (new URIBuilder(ssoConfigData.getLoginUrl())).addParameter(Constant.K_APPNAME, encodeAppName).addParameter(Constant.K_TARGET, param).build().toString();
            response.sendRedirect(url);
        } catch (Exception var8) {
            log.error("Redirect to login failed.", var8);
        }

    }

    /**
     * @Author Alan liu
     * @Description HttpServletRequest 取ticket
     * @Date 11:49 2018/12/21
     * @param request
     * @return java.lang.String
     **/
    protected String retrieveTicket(HttpServletRequest request) {
        String ticket = request.getParameter(Constant.K_TICKET);
        return ticket;
    }

    /**
     * @Author Alan liu
     * @Description 在cookie中设置session
     * @Date 11:49 2018/12/21
     * @param response
     * @param plainSessionCookieValue
     * @return void
     **/
    protected void setSession(HttpServletResponse response, String plainSessionCookieValue) {
        String sessionCookie = SsoConfigData.newInstance().getAes().encryptBase64(plainSessionCookieValue);
        Cookie cookie = new Cookie(Constant.K_SESSION_KEY, sessionCookie);
        cookie.setPath("/");
        cookie.setMaxAge(this.cookieMaxAge);
        cookie.setDomain("alan.com");
        response.addCookie(cookie);
    }

    /**
     * @Author Alan liu
     * @Description 取session并返回解密后的明文
     * @Date 11:50 2018/12/21
     * @param request
     * @return java.lang.String
     **/
    protected String retrieveSession(HttpServletRequest request) {
        String encryptedSession = SsoCookieUtils.getCookieValue(request, Constant.K_SESSION_KEY);
        if (StringUtils.isNotBlank(encryptedSession)) {
            String sessionCookie = SsoConfigData.newInstance().getAes().decryptStrFromBase64(encryptedSession);
            if (StringUtils.isNotBlank(sessionCookie)) {
                return sessionCookie;
            }
        }
        return null;
    }

}
